/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2020.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 28-3 */

/* t_clone.c

   Demonstrate the use of the Linux-specific clone() system call.
*/
#define _GNU_SOURCE
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sched.h>
#include "tlpi_hdr.h"

#ifndef CHILD_SIG
#define CHILD_SIG SIGUSR1       /* Signal to be generated on termination
								   of cloned child */
#endif

static int                      /* Startup function for cloned child */
childFunc(void *arg)
{
	if (close(*((int *) arg)) == -1)                                   // (1)
		errExit("close");

	return 0;                           /* Child terminates now */
}

int
main(int argc, char *argv[])
{
	const int STACK_SIZE = 65536;       /* Stack size for cloned child */
	char *stack;                        /* Start of stack buffer */
	char *stackTop;                     /* End of stack buffer */
	int s, fd, flags;

	fd = open("/dev/null", O_RDWR);     /* Child will close this fd */ // (2)
	if (fd == -1)
		errExit("open");

	/* If argc > 1, child shares file descriptor table with parent */

	flags = (argc > 1) ? CLONE_FILES : 0;                              // (3)

	/* Allocate stack for child */

	stack = (char*)malloc(STACK_SIZE);                                 // (4)
	if (stack == NULL)
		errExit("malloc");
	stackTop = stack + STACK_SIZE;      /* Assume stack grows downward */

	/* Ignore CHILD_SIG, in case it is a signal whose default is to
	   terminate the process; but don't ignore SIGCHLD (which is ignored
	   by default), since that would prevent the creation of a zombie. */

	if (CHILD_SIG != 0 && CHILD_SIG != SIGCHLD)                        // (5)
	{
		if (signal(CHILD_SIG, SIG_IGN) == SIG_ERR)
			errExit("signal");
	}
	
	/* Create child; child commences execution in childFunc() */

	int clerr = clone(childFunc, stackTop, flags|CHILD_SIG, (void*)&fd); // (6)
	if (clerr == -1)
		errExit("clone");

	/* Parent falls through to here. Wait for child; __WCLONE is
	   needed for child notifying with signal other than SIGCHLD. */

	if (waitpid(-1, NULL, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0) == -1)  // (7)
		errExit("waitpid");

	printf("child has terminated\n");

	/* Did close() of file descriptor in child affect parent? */

	s = write(fd, "x", 1);                                             // (8)
	if (s == -1 && errno == EBADF)
		printf("file descriptor %d has been closed\n", fd);
	else if (s == -1)
		printf("write() on file descriptor %d failed "
				"unexpectedly (%s)\n", fd, strerror(errno));
	else
		printf("write() on file descriptor %d succeeded\n", fd);

	exit(EXIT_SUCCESS);
}
